/**
 ******************************************************************************
 * @file	epcsx.c
 * @brief	epcsx 升级工具
 *
 * 1. 显示设备信息
 * ./epcsx.elf -D /dev/spidev3.0 -s
 *
 * 2. 检查设备是否存在损坏
 * ./epcsx.elf -D /dev/spidev3.0 -b
 *
 * 3. 设备所有单元填充0x2b
 * ./epcsx.elf -D /dev/spidev3.0 -c 2b
 *
 * 4. 读取设备所有存储单元保持到out.bin
 * ./epcsx.elf -D /dev/spidev3.0 -r out.bin
 *
 * 5. 从本地文件 update.bin 升级设备
 * ./epcsx.elf -D /dev/spidev3.0 -w update.bin
 *
 *
 * 在 EPCS4 验证读写正确性，芯片容量 0x80000 byte
 * $./epcsx.elf -D /dev/spidev3.0 -c 2b
 * $./epcsx.elf -D /dev/spidev3.0 -r out.bin
 * $ hexdump  out.bin
 * 0000000 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b
 * *
 * 0080000
 *
 *
 * @section Depend
 *	-# spi_mxs.ko
 *	-# spidev.ko
 *	-# libepcsx.so
 *
 * 2016-10-19,MenglongWu,MenglongWoo@aliyun.com
 */


#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#include "epcsx.h"
#include "proginfo.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))




int format_chip(struct epcs_dev *dev);
unsigned int write_chip(struct epcs_dev *dev, unsigned char *pdata, unsigned int len);
unsigned int read_chip(struct epcs_dev *dev, unsigned char *pdata, unsigned int len);
unsigned int ap_hash(unsigned char *data, unsigned int len);

#define DEF_SPI "/dev/spidev1.1"
static char *device = DEF_SPI;
static uint8_t mode = 's';
static unsigned int onebyte = 0xf0;
static uint32_t speed = 500000;
static char *ifile = "./epcs-w.bin";
static char *ofile = "./epcs-cp.bin";

static void print_usage(const char *prog)
{
	printf("Usage: %s [-Dswrcb]\n", prog);
	puts("  -D <dev>    device to use (default /dev/spidev1.1)\n"
	     "  -s <speed   max speed (Hz)\n"
	     "  -w <file>   write chip from file \n"
	     "  -r <file>   read chip to file \n"
	     "  -c <byte>   fill all cell with <byte>\n"
	     "  -b          check chip is bad\n");
	exit(1);
}

static void parse_opts(int argc, char *argv[])
{
	while (1) {
		static const struct option lopts[] = {
			{ "device",  1, 0, 'D' },
			{ "speed",   1, 0, 's' },
			{ "delay",   1, 0, 'w' },
			{ "bpw",     1, 0, 'r' },
			{ "check",   1, 0, 'c' },
			{ "bad",     0, 0, 'b' },
			{ NULL, 0, 0, 0 },
		};
		int c;

		c = getopt_long(argc, argv, "D:s:w:r:c:b", lopts, NULL);

		if (c == -1) {
			break;
		}

		switch (c) {
		case 'D':
			device = optarg;
			break;
		case 's':
			speed = atoi(optarg);
			break;
		case 'w':
			ifile = optarg;
			mode = 'w';
			break;
		case 'r':
			ofile = optarg;
			mode = 'r';
			break;
		case 'c':
			sscanf(optarg, "%x", &onebyte);
			mode = 'c';
			break;
		case 'b':
			mode = 'b';
			break;
		default:
			print_usage(argv[0]);
			break;
		}
	}
}
static void print_configure()
{
	printf("Device:\t\t%s\n", device);
	if(mode == 'w') {
		printf("Input file: \t%s\n", ifile);
	} else if (mode == 'r') {
		printf("Output file: \t%s\n", ofile);
	}
	printf("Speed:\t\t%d\n\n", speed);
}
static void dev_info(struct epcs_dev *dev)
{
	printf("device information\n");
	printf(
	    "\tDevice ID       %x %s\n"
	    "\tnum sector      %d \n"
	    "\tpage per sector %d \n"
	    "\tbyte per page   %d \n"
	    "\tsector  size    %d \n"
	    "\ttotal byte      %d \n",
	    dev->pid, dev->desc,
	    (int)dev->num_sector,
	    (int)dev->page_per_sector,
	    (int)dev->byte_per_page,
	    (int)dev->sector_sz,
	    (int)epcs_size(dev));
}

int update_file(struct epcs_dev *dev, char *filename)
{
	printf("\n****** Start update chip from %s ******\n", filename);
	int ret;

	FILE *fp ;
	unsigned int  dev_size = epcs_size(dev);
	unsigned char          *file_buf;
	int file_len;
	int wr_len, offset;

	fp = fopen(filename, "rb");
	if (fp == NULL) {
		ret = -1;
		goto _fail1;
	}



	fseek(fp, 0, SEEK_END);
	file_len = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	if (dev_size < file_len) {
		printf("Input file too large,device not enough space!\n");
		ret = -1;
		goto _fail1;
	}
	file_buf = (unsigned char *)malloc(dev_size);
	if (file_buf == NULL ) {
		ret = -1;
		goto _fail1;
	}

	offset = 0;
	while(file_len) {
		wr_len = fread(file_buf + offset, 1, file_len, fp);
		file_len -= wr_len;
		offset += file_len;
	}

	fseek(fp, 0, SEEK_END);
	file_len = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	write_chip(dev, file_buf, dev_size);
	printf("check sum %x\n", ap_hash(file_buf, dev_size));
	read_chip(dev, file_buf, dev_size);
	printf("check sum %x\n", ap_hash(file_buf, dev_size));

	ret = 0;
	free(file_buf);

	fclose(fp);
_fail1:
	if (ret == 0) {
		printf("\n****** Update success ******\n");
	} else {
		printf("\n****** Update fail ******\n");
	}
	return ret;
}

int save_file(struct epcs_dev *dev, char *filename)
{
	printf("\n****** Read chip save as %s ******\n", filename);
	int ret;

	FILE *fp ;
	unsigned long dev_size = epcs_size(dev);
	unsigned char *file_buf;
	int file_len;
	int wr_len, offset;

	fp = fopen(filename, "wb");
	if (fp == NULL) {
		ret = -1;
		goto _fail1;
	}
	file_len = dev_size;
	file_buf = (unsigned char *)malloc(dev_size);
	if (file_buf == NULL ) {
		ret = -1;
		goto _fail1;
	}

	read_chip(dev, file_buf, dev_size);
	printf("check sum %x\n", ap_hash(file_buf, dev_size));



	offset = 0;
	while(file_len) {
		wr_len = fwrite(file_buf + offset, 1, file_len, fp);
		printf("wr_len %d\n", wr_len);
		file_len -= wr_len;
		offset += file_len;
	}

	ret = 0;
	free(file_buf);

	fclose(fp);
_fail1:

	if (ret == 0) {
		printf("\n****** Save success ******\n");
	} else {
		printf("\n****** Save fail ******\n");
	}
	return ret;

}

int fill(struct epcs_dev *dev, char date)
{
	unsigned long dev_size;
	unsigned char *ifile;

	printf("\n****** Start fill byte 0x%.2x ******\n", date);
	dev_size = epcs_size(dev);
	ifile = (unsigned char *)malloc(dev_size);
	if (ifile == NULL) {
		perror("malloc error");
		return -1;
	}

	memset(ifile, date, dev_size);
	write_chip(dev, ifile, dev_size);
	free(ifile);
	printf("\n****** Fill byte over ******\n");
	return 0;
}

int isbad(struct epcs_dev *dev)
{
	printf("\n****** Check bad cell ******\n");
	unsigned long dev_size;
	unsigned char *ofile;
	dev_size = epcs_size(dev);
	ofile = (unsigned char *)malloc(dev_size);
	if (ifile == NULL) {
		perror("malloc error");
		return -1;
	}

	// 检查全0xff
	printf("\nStep 1: format device\n");
	format_chip(dev);
	read_chip(dev, ofile, dev_size);
	for (int i = 0; i < dev_size; i++) {
		if (ofile[i] != 0xff) {
			goto _fail;
		}
	}
	printf("\nStep 2: write 0x00 to all cell\n");
	// 检查全0x00
	bzero(ofile, dev_size);
	write_chip(dev, ofile, dev_size);
	memset(ofile, 1, dev_size);
	read_chip(dev, ofile, dev_size);
	for (int i = 0; i < dev_size; i++) {
		if (ofile[i] != 0x00) {
			printf("i %d", i);
			goto _fail;
		}
	}
	printf("\n****** Chip is good ******\n");


	free(ofile);
	return 0;
_fail:
	printf("\n****** Chip is bad ******\n");
	free(ofile);
	return -1;

}

int main(int argc, char *argv[])
{
	// epcs_close(NULL);
	int ret = 0;
	// int fd;
	parse_opts(argc, argv);
	print_configure();
	struct epcs_dev *dev;

	ret = epcs_open(device, &dev, 5000000);
	switch(ret) {
	case 0:
		printf("open device success!\n");
		break;
	case -1:
		printf("bus error\n");
		break;
	case -2:
		printf("unknow device ID\n");
		return -1;
	}
	if (dev == 0) {
		// printf("open")
		return 0;
	}
	switch(mode) {
	case 'w':
		dev_info(dev);
		update_file(dev, ifile);
		break;
	case 'r':
		dev_info(dev);
		save_file(dev, ofile);
		break;
	case 's':
		dev_info(dev);
		break;
	case 'c':
		fill(dev, (char)onebyte);
		break;
	case 'b':
		isbad(dev);
		break;
	}
	epcs_close(dev);
	return 0;



	unsigned int dev_size;


	dev_size = epcs_size(dev);

	printf("dev_size %d\n", (int)dev_size);
	unsigned char *ifile = (unsigned char *)malloc(dev_size);
	unsigned char *ofile = (unsigned char *)malloc(dev_size);

	// dev_size = 65535;
	memset(ifile, 6, dev_size);
	memset(ofile, 0, dev_size);

	write_chip(dev, ifile, (int)dev_size);
	printf("check sum %x\n", ap_hash(ifile, dev_size));
	read_chip(dev, ofile, (int)dev_size);
	printf("check sum %x\n", ap_hash(ofile, dev_size));

	epcs_close(dev);
	return 0;
}


int format_chip(struct epcs_dev *dev)
{
	printf("\nErase Chip\n");
	if (epcs_erase_bulk(dev) == -1) {
		printf("Erase timeout\n");
		return -1;
	} else {
		printf("\nErase Finish\n");
		return 0;
	}

}

unsigned int write_chip(struct epcs_dev *dev, unsigned char *pdata, unsigned int len)
{
	format_chip(dev);

	unsigned long page_total;
	unsigned long page_mask = dev->byte_per_page - 1;
	unsigned long page_size = dev->byte_per_page;
	unsigned long page_addr;
	unsigned long off_data, size;
	// epcs_size(dev);

	//计算文件大小，确定需写入多少Page总数
	page_total  = 	(
	                    (len + page_mask ) & (~page_mask)
	                )	/ page_size;


	//Step 2: 写入文件
	page_addr = 0;
	off_data = 0;
	while(off_data < len ) {
		//当写入的内容大于1页容量，则写入1页大小，否则写入剩余大小
		if(len - off_data > page_size) {
			size = page_size;
		} else {
			size = len - off_data;
		}


		printf("\rwrite page %8.8x", (unsigned int)page_addr);
		epcs_write_page(dev, page_addr, pdata + off_data, size);
		// while(epcs_read_status(dev) != 0);


		//跟新数据偏偏移量和页面地址
		off_data  += page_size;
		page_addr += page_size;
	}
	printf("\n");
	return 0;
}
unsigned int read_chip(struct epcs_dev *dev, unsigned char *pdata, unsigned int len)
{
	unsigned long page_total;
	unsigned long page_mask = dev->byte_per_page - 1;
	unsigned long page_size = dev->byte_per_page;
	unsigned long page_addr;
	unsigned long off_data, size;


	//计算文件大小，确定需读出多少Page总数
	page_total  = 	(
	                    (len + page_mask ) & (~page_mask)
	                )	/ page_size;

	printf("Total %d page be read (0x%8.8x ~ 0x%8.8x)\n",
	       (int)page_total,    
	       0,          
	       (int)(0 + page_total * page_size - 1));


	//等待空闲
	//避免在epcs4_read_file调用前有擦除、写入等操作造成第一次读出数据有错

	//Step 1:读出数据
	page_addr = 0;
	off_data = 0;
	while(off_data < len ) {
		if(len - off_data > page_size) {
			size = page_size;
		} else {
			size = len - off_data;
		}

		//写入前等待空闲状态
		// while(epcs_read_status(dev) != 0);

		printf("\rread page %8.8x", (unsigned int)page_addr);

		epcs_read_page(dev, page_addr, pdata + off_data, size);


		//跟新数据偏偏移量和页面地址
		off_data  += page_size;
		page_addr += page_size;
	}
	printf("\n");
	return 0;
}




unsigned int ap_hash(unsigned char *data, unsigned int len)
{
	long hash = 0xaaaaaaaa;
	for (int i = 0; i < len; i++) {
		if((i & 1 ) == 0) {
			hash ^= ((hash << 7) ^ data[i] ^ (hash >> 3));
		} else {
			hash ^= (~((hash << 11) ^ data[i] ^ (hash >> 5)));
		}
	}
	return hash;
}







#ifdef PRJ_NAME
	PROG_INFO(project, PRJ_NAME);
#endif

#if  defined(PRJ_VERSION) && defined(PRJ_PATCHLEVEL) && defined(PRJ_SUBLEVEL)
	PROG_INFO(version, PRJ_VERSION "." PRJ_PATCHLEVEL "." PRJ_SUBLEVEL);
#endif

#ifdef BUILD_DATE
	PROG_INFO(build, BUILD_DATE);
#endif

PROG_INFO(Author, "MenglongWu");
PROG_INFO(depend, "libepcsx.so");
PROG_INFO(description, "EPCSx download/read tool");
